/*
 * This file referenced Lionel Brits & Jeff Molofee's openGL Tutorials,The tutorials get from http://nehe.gamedev.net/
 */
#define GL_GLEXT_PROTOTYPES

#include <GL/glut.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <math.h>

#define IMG_W 32
#define IMG_H 32

#define PARTICLE_COUNT 1000

float slowdown = 2.0f;
float xspeed;
float yspeed;
float zoom = -20.0f;

GLuint texture[1];			// storage for particle textures

struct particle{
	float life;
	float fade;						// fade speed
	float r;
	float g;
	float b;
	float x;						// x,y,z position
	float y;
	float z;
	float xi;						// x,y,z deriction
	float yi;
	float zi;
};

struct particle particles[PARTICLE_COUNT];

static GLfloat colors[12][3] = {
	{1.0f,0.5f,0.5f},{1.0f,0.75f,0.5f},{1.0f,1.0f,0.5f},{0.75f,1.0f,0.5f},
	{0.5f,1.0f,0.5f},{0.5f,1.0f,0.75f},{0.5f,1.0f,1.0f},{0.5f,0.75f,1.0f},
	{0.5f,0.5f,1.0f},{0.75f,0.5f,1.0f},{1.0f,0.5f,1.0f},{1.0f,0.5f,0.75f}
};

GLubyte *loadBmp(const char *filename,unsigned int w,unsigned int h)
{
	unsigned char *buf;
	int fd = open(filename,O_RDONLY);
	if(fd < 0){
		printf("open file failed,errno:%s\n",strerror(errno));
		return NULL;
	}
	unsigned long buf_count = w * h * 3 * sizeof(unsigned char);
	buf = (unsigned char *)malloc(buf_count);
	if(!buf){
		printf("alloc memery failed,errno:%s\n",strerror(errno));
		close(fd);
		return NULL;
	}
	lseek(fd,54,SEEK_SET);
	if(read(fd,buf,buf_count) <= 0){
		printf("read file failed,errno:%s\n",strerror(errno));
		close(fd);
		free(buf);
		return NULL;
	}
	close(fd);
	return buf;
}

void set_particles(void)
{
	int i;
	for(i = 0;i < PARTICLE_COUNT;i++){
		particles[i].life = 1.0f;								// give all the particles full life
		particles[i].fade = (float)(random() % 100) / 1000.0f + 0.003f;		// random fade speed
		particles[i].r = colors[i % 12][0];	// select rainbow color
		particles[i].g = colors[i % 12][1];
		particles[i].b = colors[i % 12][2];
		particles[i].x = 0.0f;
		particles[i].y = 0.0f;
		particles[i].z = 0.0f;
		particles[i].xi = ((float)(random() % 50) - 26.0f) * 10.0f;			// random speed on x,y,z axis
		particles[i].yi = ((float)(random() % 50) - 25.0f) * 10.0f;
		particles[i].zi = ((float)(random() % 50) - 25.0f) * 10.0f;
	}
}

void init(void)
{
	GLubyte *image = loadBmp("particle.bmp",IMG_W,IMG_H);

	glGenTextures(1,&texture[0]);						// Create Three Textures
	glBindTexture(GL_TEXTURE_2D,texture[0]);
	glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
	glTexImage2D(GL_TEXTURE_2D,0,3,IMG_W,IMG_H,0,GL_RGB,GL_UNSIGNED_BYTE,image);

	free(image);

	glShadeModel(GL_SMOOTH);
	glClearColor(0.0f,0.0f,0.0f,0.0f);
	glClearDepth(1.0f);
	glDisable(GL_DEPTH_TEST);
	glEnable(GL_BLEND);
	glBlendFunc(GL_SRC_ALPHA,GL_ONE);
	glHint(GL_PERSPECTIVE_CORRECTION_HINT,GL_NICEST);
	glHint(GL_POINT_SMOOTH_HINT,GL_NICEST);
	glEnable(GL_TEXTURE_2D);

	set_particles();
}

void display(void)
{
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	glLoadIdentity();

	int i;
	for(i = 0;i < PARTICLE_COUNT;i++){
		float x = particles[i].x;
		float y = particles[i].y;
		float z = particles[i].z + zoom;

		// draw the particle using RGB values,fade the particle based on it's life
		glColor4f(particles[i].r,particles[i].g,particles[i].b,particles[i].life);

		glBegin(GL_TRIANGLE_STRIP);						// build quad from a triangle strip
			glTexCoord2d(1,1);							// top right
			glVertex3f(x + 0.5f,y + 0.5f,z);
			glTexCoord2d(0,1);							// top left
			glVertex3f(x - 0.5f,y + 0.5f,z);
			glTexCoord2d(1,0);							// bottom right
			glVertex3f(x + 0.5f,y - 0.5f,z);
			glTexCoord2d(0,0);							// bottom left
			glVertex3f(x - 0.5f,y - 0.5f,z);
		glEnd();

		particles[i].x += particles[i].xi / (slowdown * 1000);	// move on the x axis by x speed
		particles[i].y += particles[i].yi / (slowdown * 1000);	// move on the y axis by y speed
		particles[i].z += particles[i].zi / (slowdown * 1000);	// move on the z axis by z speed

		particles[i].xi += xspeed;						// take pull on x axis into account
		particles[i].yi += yspeed;						// take pull on y axis into account

		particles[i].life -= particles[i].fade;			// reduce particles life by 'fade'

		if(particles[i].life < 0.0f){					// if particle is burned out
			particles[i].life = 1.0f;					// give it new life
			particles[i].fade = (float)(random() % 100) / 1000.0f + 0.003f;
			particles[i].x = 0.0f;						// center on x,y,z axis
			particles[i].y = 0.0f;
			particles[i].z = 0.0f;
			particles[i].xi = xspeed + (float)(random() % 60) - 32.0f;	// x axis speed and direction
			particles[i].yi = yspeed + (float)(random() % 60) - 32.0f;	// y axis speed and direction
			particles[i].zi = (float)(random() % 60) - 30.0f;			// z axis speed and direction
		}
	}

	glFlush();
}

void reset_display(void)
{
	glutIdleFunc(NULL);

	set_particles();

	glutSwapBuffers();
}

void spin_display(void)
{
	glutPostRedisplay();
}

void reshape(int w, int h)
{
	glViewport(0,0,w,h);								// Reset The Current Viewport

	glMatrixMode(GL_PROJECTION);						// Select The Projection Matrix
	glLoadIdentity();									// Reset The Projection Matrix

	// Calculate The Aspect Ratio Of The Window
	gluPerspective(45.0f,(GLfloat)w / (GLfloat)h,0.1f,100.0f);

	glMatrixMode(GL_MODELVIEW);							// Select The Modelview Matrix
	glLoadIdentity();									// Reset The Modelview Matrix
}

void keyboard (unsigned char key, int x, int y)
{
	switch (key){
		case 'a':
		case 'A':
			glutIdleFunc(spin_display);
			break;
		case 's':
		case 'S':
			glutIdleFunc(NULL);
			break;
		case 'r':
		case 'R':
			reset_display();
			break;
		case '8':
			yspeed += 0.2f;
			break;
		case '2':
			yspeed -= 0.2f;
			break;
		case '4':
			xspeed -= 0.2f;
			break;
		case '6':
			xspeed += 0.2f;
			break;
		case 27:
			exit(0);
			break;
		default:
			return;
	}
	glutPostRedisplay();
}

int main(int argc, char** argv)
{
	glutInit(&argc, argv);
	glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB | GLUT_DEPTH);
	glutInitWindowSize(800, 600);
	glutInitWindowPosition(100, 100);
	glutCreateWindow(argv[0]);
	init();
	glutDisplayFunc(display);
	glutReshapeFunc(reshape);
	glutKeyboardFunc(keyboard);
	glutMainLoop();
	return 0;
}
